﻿
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using STP;

namespace Simulator
{
	#region UInt8HexTypeConverter
	class UInt8HexTypeConverter : TypeConverter
	{
		public override bool CanConvertFrom (ITypeDescriptorContext context, Type sourceType)
		{
			if (sourceType == typeof (string))
				return true;
			else
				return base.CanConvertFrom (context, sourceType);
		}

		public override bool CanConvertTo (ITypeDescriptorContext context, Type destinationType)
		{
			if (destinationType == typeof (string))
				return true;
			else
				return base.CanConvertTo (context, destinationType);
		}

		public override object ConvertTo (ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
		{
			if ((destinationType == typeof (string)) && (value is byte))
				return String.Format ("0x{0:X2}", value);
			else
				return base.ConvertTo (context, culture, value, destinationType);
		}

		public override object ConvertFrom (ITypeDescriptorContext context, CultureInfo culture, object value)
		{
			if (value.GetType () == typeof (string))
			{
				String input = (String) value;

				if (input.StartsWith ("0x", StringComparison.OrdinalIgnoreCase))
				{
					input = input.Substring (2);
				}

				return Byte.Parse (input, NumberStyles.HexNumber, culture);
			}
			else
			{
				return base.ConvertFrom (context, culture, value);
			}
		}
	}
	#endregion

	#region UInt16HexTypeConverter
	class UInt16HexTypeConverter : TypeConverter
	{
		public override bool CanConvertFrom (ITypeDescriptorContext context, Type sourceType)
		{
			if (sourceType == typeof (string))
				return true;
			else
				return base.CanConvertFrom (context, sourceType);
		}

		public override bool CanConvertTo (ITypeDescriptorContext context, Type destinationType)
		{
			if (destinationType == typeof (string))
				return true;
			else
				return base.CanConvertTo (context, destinationType);
		}

		public override object ConvertTo (ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
		{
			if ((destinationType == typeof (string)) && (value is ushort))
				return String.Format ("0x{0:X4}", value);
			else
				return base.ConvertTo (context, culture, value, destinationType);
		}

		public override object ConvertFrom (ITypeDescriptorContext context, CultureInfo culture, object value)
		{
			if (value.GetType () == typeof (string))
			{
				String input = (String) value;

				if (input.StartsWith ("0x", StringComparison.OrdinalIgnoreCase))
				{
					input = input.Substring (2);
				}

				return UInt16.Parse (input, NumberStyles.HexNumber, culture);
			}
			else
			{
				return base.ConvertFrom (context, culture, value);
			}
		}
	}
	#endregion

	#region BridgeAddressTypeConverter
	class BridgeAddressTypeConverter : TypeConverter
	{
		public override bool CanConvertFrom (ITypeDescriptorContext context, Type sourceType)
		{
			if (sourceType == typeof (string))
				return true;
			else
				return base.CanConvertFrom (context, sourceType);
		}

		public override bool CanConvertTo (ITypeDescriptorContext context, Type destinationType)
		{
			if (destinationType == typeof (string))
				return true;
			else
				return base.CanConvertTo (context, destinationType);
		}

		static readonly FormatException ParseFormatException = new FormatException ("String must have the form AABBCCDDEEF or AA-BB-CC-DD-EE-FF or AA:BB:CC:DD:EE:FF");

		static bool IsHexDigit (Char c)
		{
			return ((c >= '0') && (c <= '9'))
				|| ((c >= 'a') && (c <= 'f'))
				|| ((c >= 'A') && (c <= 'F'));
		}

		static byte GetHexValue (Char c)
		{
			if ((c >= '0') && (c <= '9'))
				return (byte) (c - '0');

			if ((c >= 'a') && (c <= 'f'))
				return (byte) (c - 'a' + 10);

			if ((c >= 'A') && (c <= 'F'))
				return (byte) (c - 'A' + 10);

			Debug.Assert (false);
			return 0;
		}

		public static new ulong ConvertFromString (string input)
		{
			char separator;
			if (input.Contains ("-"))
				separator = '-';
			else if (input.Contains (":"))
				separator = ':';
			else
				separator = '\0';

			if (separator != 0)
			{
				if (input.Length != 17)
					throw ParseFormatException;

				if ((input [2] != separator) || (input [5] != separator) || (input [8] != separator) || (input [11] != separator) || (input [14] != separator))
					throw ParseFormatException;
			}
			else
			{
				if (input.Length != 12)
					throw ParseFormatException;
			}

			UInt64 address = 0;
			int pos = 0;
			for (int i = 0; i < 6; i++)
			{
				if (!IsHexDigit (input [pos]) || !IsHexDigit (input [pos + 1]))
					throw ParseFormatException;

				address = (address << 8) | (byte) (GetHexValue (input [pos]) << 4) | GetHexValue (input [pos + 1]);
				pos += 2;

				if (separator != 0)
					pos++;
			}

			return address;
		}

		public override object ConvertFrom (ITypeDescriptorContext context, CultureInfo culture, object value)
		{
			if (value.GetType () == typeof (string))
			{
				var str = (String) value;
				var address = ConvertFromString (str);
				return (ulong) address;
			}
			else
			{
				return base.ConvertFrom (context, culture, value);
			}
		}

		public enum ConvertToStringOptions
		{
			NoSeparator,
			SeparatorDash,
			SeparatorColon,
		}

		public static string ConvertToString (ulong address, ConvertToStringOptions options)
		{
			if (options == ConvertToStringOptions.NoSeparator)
			{
				return string.Format ("{0:X12}", address);
			}
			else if (options == ConvertToStringOptions.SeparatorColon)
			{
				return String.Format ("{0:X2}:{1:X2}:{2:X2}:{3:X2}:{4:X2}:{5:X2}",
					(byte) (address >> 40),
					(byte) (address >> 32),
					(byte) (address >> 24),
					(byte) (address >> 16),
					(byte) (address >> 8),
					(byte) address);
			}
			else if (options == ConvertToStringOptions.SeparatorDash)
			{
				return String.Format ("{0:X2}-{1:X2}-{2:X2}-{3:X2}-{4:X2}-{5:X2}",
					(byte) (address >> 40),
					(byte) (address >> 32),
					(byte) (address >> 24),
					(byte) (address >> 16),
					(byte) (address >> 8),
					(byte) address);
			}
			else
				throw new ArgumentException ("InvalidArgument", "options");
		}

		public override object ConvertTo (ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
		{
			if ((destinationType == typeof (string)) && (value is ulong))
			{
				var result = ConvertToString ((ulong) value, ConvertToStringOptions.SeparatorColon);
				return result;
			}
			else
				return base.ConvertTo (context, culture, value, destinationType);
		}
	}
	#endregion
}
